package demo.java.util.zip;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

import demo.java.io.IoDemo;

/**
 * <h1>ZIP和GZIP的区别</h1>
 * <p>
 * gzip是一种文件压缩工具（或该压缩工具产生的压缩文件格式），它的设计目标是处理单个的文件。gzip在压缩文件中的数据时使用的就是zlib。
 * 为了保存与文件属性有关的信息，gzip需要在压缩文件（*.gz）中保存更多的头信息内容，而zlib不用考虑这一点。
 * 但gzip只适用于单个文件，所以我们在UNIX/Linux上经常看到的压缩包后缀都是*.tar.gz或*.tgz，也就是先用tar把多个文件打包成单个文件，再用gzip压缩的结果。
 * <p>
 * zip只是一种数据结构，跟rar同类型。zip是适用于压缩多个文件的格式（相应的工具有PkZip和WinZip等），因此，zip文件还要进一步包含文件目录结构的信息，比gzip的头信息更多。
 * 但需要注意，zip格式可采用多种压缩算法，我们常见的zip文件大多不是用zlib的算法压缩的，其压缩数据的格式与gzip大不一样。
 * 
 * <h1>相关类与接口：</h1>
 * 
 * <li>Checksum 接口：表示数据校验和的接口,被类Adler32和CRC32实现
 * <li>Adler32 ：使用Alder32算法来计算Checksum数目
 * <li>CRC32 ：使用CRC32算法来计算Checksum数目
 * 
 * <li>CheckedInputStream ：InputStream派生类，可得到输入流的校验和Checksum,用于校验数据的完整性
 * <li>CheckedOutputStream ：OutputStream派生类，可得到输出流的校验Checksum， 用于校验数据的完整性
 * 
 * <li>DeflaterOutputStream ：压缩类的基类。
 * <li>ZipOutputStream ：DeflaterOutputStream的一个子类，把数据压缩成Zip文件格式
 * <li>GZIPOutputStream ：DeflaterOutputStream的一个子类，把数据压缩成GZip文件格式
 * 
 * <li>InflaterInputStream ：解压缩类的基类
 * <li>ZipInputStream ：InflaterInputStream的一个子类，能解压缩Zip格式的数据
 * <li>GZIPInputStream ：InflaterInputStream的一个子类，能解压缩Zip格式的数据
 * 
 * <li>ZipEntry 类：表示 ZIP 文件条目
 * <li>ZipFile 类：此类用于从 ZIP 文件读取条目
 *
 */
public class ZipDemo {

    private static Logger logger = LoggerFactory.getLogger(ZipDemo.class);

    private static String zip = "H4sIAAAAAAAAAIuuVspLzE1VslIyNLU0NjOztLSwMFfSUSrIyM9LNUQTrtWBq342t+/Z3H0oCg0NYFwjsD4DSwMzUwtDI7hxxmBhC2NLSyNLQyNjTOOMUC02MDUwMjUxMzFUqo0FABFIe86oAAAA";

    @Test
    public void demo8() {
        String a = zip;
        String b = ungzipString(a);
        logger.info(b);
        JSONArray jsonArray = JSONObject.parseArray(b);
        String j = JSONObject.toJSONString(jsonArray, SerializerFeature.DisableCircularReferenceDetect);
        logger.info(j);
        String c = JSONObject.parse(b).toString();
        logger.info(c);
    }

    /**
     * 消除对同一对象循环引用的问题，默认为false
     */
    @Test
    public void disableCircularReferenceDetect() {
        String a = "H4sIAAAAAAAAANVWS0/bShT+L1aXUPmRF152XXXFrrcLRHOlSKGgkFaqqkrkBU5J4tCGlhCnoSlOuVzAqRKI47z+jM94vMpfuGdiyuO2RGVThDSKJmfOnDnffGe+46dvuMVwNPp4eZGTcRqJv+ZkzjbfkdMON8OtxJZfRV4shi9tb2e48EIsGgmvxucjS2xB5IXQrCDO8tK84JN9osxLD+f8ftwdXYj/jttKOBx78nIJfYSgxAvBUIjng2hfXVp9tPyc5UN2DGc3jab4j1giP8sHZ0Vpnp+TRV6WBLb4egUXhRnu1UI08pyT47GX4bcz1/A9iIX/xv0PnvLPHv4w3wkiMLq2tW4P9uzeEUKjxjYUj6gxhEyetrtoQQcofvs1ZEn2BWXhErI4DfJdUyoFpIBfEgK87zqlyQPQB+N+jihbSC8ouySbd3pltJOaSoYaKZ9RJQ8ndaJ0nJ7qFNehv0dKa94WvDb3S9epnIDWhOqaPcjbVgGsDjlt0I5hm2xOh0M2t/bJ9w3YT4/7itMbof/V+KSgwzBjm8cwSo37WQzunXIlbXZg6QyUJjU6UPkM+/lxP3GVGLyEc2KCsg/vIXTLWhT/VC3eRMUE3TRE4v0oNb8oCsKcFOCFaaXm0e9+LaCFjsq2eYh2UDvO3jRWpcAtWfX9KVZvQI2Qp8Hh7welISaffFAMXi9Zt6yAmoSM4pRqjNjrDLNXnGmhINB2hxwNyceuW62jj5sagGrYPR3eH1ADhWXL24Jyi1VgWxZNlJxWD/2hr2KcK6ezkJMSQREgqT043kERYFqlZV1tjTYS7q5KPp2dB8B6an73MnL0KrphmqTdI+ksjP5loTq6PdDcLxYo/2Ccv178JDA52qzjmK40ou+edT1G26ABxcr/CLPNDRRrpzyk7W9QqRC9T86SpKrjLxR6oFRIWnWGBaIZnlIjYaR9SNcSYCrsOs0Txm497VgN7KoC0RTBh3Sh4pOdGtFq5JOO4o4FC3lF4J1kV/CDso5RvKAX3ZfFmvSTcX/XK28wk9hSbFMjH5uQs2DrAP+62xveZHreeLzdLdL2Mahf6agCGUwi51oNGHyAdy1QW3TYdUqnpG44lTbGOe8/6iZJZYhadNffYxoXuTmKiQMaCg660YLmlm2yvueWD6GYg6ZKtE3cC9k8JjCRthqWlG1iY1S8071QoFmIER0QIPvgmHTdy+3FFF4P4qX6yPPBlwGFbZJljRefjj2qUiNBKm3k7+LDhWwr5LP2c6NkGZqbv1nNwm375l1X83SFFW5sms/+A92B0QR0CwAA";
        String b = ungzipString(a);
        logger.info(b);
        JSONArray jsonArray = JSONObject.parseArray(b);

        String j = JSONObject.toJSONString(jsonArray, SerializerFeature.DisableCircularReferenceDetect);
        logger.info(j);
        String c = JSONObject.parse(b).toString();
        logger.info(c);
    }

    /**
     * 
     * <li>字符串压缩前后对比，压缩比达到7%。
     * <li>耗时测试
     */
    @Test
    public void demo1() throws IOException {
        String json = IoDemo.readStringFromFile(new File(
                "E:\\Projects\\Stan\\Demo\\testJava\\src\\main\\java\\demo\\json\\mx_carrier_bills_notify.json"));

        byte[] src = json.getBytes(StandardCharsets.UTF_8);
        String base64Str = Base64.getEncoder().encodeToString(src);
        System.out.println(base64Str);

        logger.info("原始数据 :{} characters, BASE64编码后：{} character", json.length(), base64Str.length());
        logger.info("原始数据 :{} bytes, BASE64编码后：{} bytes", src.length, base64Str.getBytes().length);

        long t0 = System.currentTimeMillis();
        byte[] gzip = gzip(src);
        long t1 = System.currentTimeMillis();
        logger.info("压缩{} bytes 耗时:{}ms，压缩后:{} bytes，压缩比：{}", src.length, (t1 - t0), gzip.length,
                ((double) gzip.length / src.length));
        String base64Gzip = Base64.getEncoder().encodeToString(gzip);
        long t2 = System.currentTimeMillis();
        byte[] base64GzipBytes = base64Gzip.getBytes(StandardCharsets.UTF_8);
        logger.info("BASE64编码{} bytes耗时:{}ms ,BASE64编码后: {}bytes", gzip.length, (t2 - t1), base64GzipBytes.length);
    }

    /**
     * <li>压缩、解压耗时:压缩比解压耗时
     * 
     */
    @Test
    public void demo2() throws IOException {
        String json = IoDemo.readStringFromFile(new File(
                "E:\\Projects\\Stan\\Demo\\testJava\\src\\main\\java\\demo\\json\\mx_carrier_bills_notify.json"));
        long t1 = System.currentTimeMillis();
        byte[] src = json.getBytes(StandardCharsets.UTF_8);
        byte[] gzip = gzip(src);
        long t2 = System.currentTimeMillis();
        logger.info("压缩 {} bytes 耗时:{}ms,压缩后：{} bytes", src.length, (t2 - t1), gzip.length);
        byte[] unGzip = unGzip(gzip);
        long t3 = System.currentTimeMillis();
        logger.info("解压{} bytes 耗时:{}ms,解压后：{} bytes", gzip.length, (t3 - t2), unGzip.length);
        String newJson = new String(unGzip, StandardCharsets.UTF_8);
        logger.info("压缩并解压后字符串是否相等：{}", json.equals(newJson));
    }

    /**
     * <li>持续压缩,对压缩文件再次压缩，字节数会微涨
     */
    @Test
    public void demo3() throws IOException {
        String json = IoDemo.readStringFromFile(new File(
                "E:\\Projects\\Stan\\Demo\\testJava\\src\\main\\java\\demo\\json\\mx_carrier_bills_notify.json"));
        byte[] src = json.getBytes(StandardCharsets.UTF_8);
        for (int i = 1; i < 10; i++) {
            long t0 = System.currentTimeMillis();
            byte[] gzip = gzip(src);
            long t1 = System.currentTimeMillis();
            logger.info("第 {} 次压缩耗时 {} ms：压缩前 {} bytes,压缩后 {} bytes", i, (t1 - t0), src.length, gzip.length);
            logger.info("压缩前 - 压缩后，相差{} bytes.", (src.length - gzip.length));
            src = gzip;
        }
    }

    /**
     * gzip压缩
     * 
     * @param src
     * @return
     * @throws IOException
     */
    public static byte[] gzip(byte[] src) throws IOException {
        if (src == null || src.length == 0) {
            return src;
        }
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
                GZIPOutputStream gout = new GZIPOutputStream(out)) {
            gout.write(src);
            gout.finish();
            return out.toByteArray();
        }
    }

    /**
     * 将压缩并Base64后的字符串进行解密解压
     *
     * @param compressedStr
     *            待解密解压字符串
     * @return
     * @throws IOException
     */
    public static byte[] unGzip(byte[] src) throws IOException {
        if (src == null || src.length == 0) {
            return src;
        }
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
                ByteArrayInputStream in = new ByteArrayInputStream(src);
                GZIPInputStream gin = new GZIPInputStream(in)) {
            byte[] buffer = new byte[1024];
            int offset;
            while ((offset = gin.read(buffer)) != -1) {
                out.write(buffer, 0, offset);
            }
            return out.toByteArray();
        }
    }

    /**
     * 将字符串压缩后Base64
     *
     * @param primStr
     *            待加压加密函数
     * @return
     */
    public static String gzipString(String primStr) {
        if (primStr == null || primStr.length() == 0) {
            return primStr;
        }
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
                GZIPOutputStream gout = new GZIPOutputStream(out)) {
            gout.write(primStr.getBytes(StandardCharsets.UTF_8));
            gout.finish();
            return Base64.getEncoder().encodeToString(out.toByteArray());
        } catch (IOException e) {
            logger.error("压缩失败", e);
            return null;
        }
    }

    /**
     * 将压缩并Base64后的字符串进行解密解压
     *
     * @param compressedStr
     *            待解密解压字符串
     * @return
     */
    public static final String ungzipString(String compressedStr) {
        if (compressedStr == null) {
            return null;
        }
        byte[] compressed = Base64.getDecoder().decode(compressedStr.replaceAll("\r|\n", ""));
        try (ByteArrayOutputStream out = new ByteArrayOutputStream();
                ByteArrayInputStream in = new ByteArrayInputStream(compressed);
                GZIPInputStream gin = new GZIPInputStream(in)) {
            byte[] buffer = new byte[1024];
            int offset;
            while ((offset = gin.read(buffer)) != -1) {
                out.write(buffer, 0, offset);
            }
            return out.toString("utf-8");
        } catch (IOException e) {
            return null;
        }
    }
}
